/**
 * 
 */
package controller.notify;

import components.exceptions.UnexpectedNotifyReceivedException;
import components.register.RegisterGroup;

import controller.Notifiable;

/**
 * 
 * NotifyBus is used to control the notify between 3 Controllers. When the hold
 * is set, the sendNotify() function would hold the notify type and notify
 * target in a temporary buffer. When nextStep() is called, the notify buffers
 * will be sent to the target controller.
 * 
 * @author Chz
 * 
 */
public class NotifyBus {

	private static NotifyBus self;
	private Notifiable tmpTargetBuffer;
	private Notify tmpNotifyBuffer;

	/**
	 * When only hold is set true while verbose is false, then stop on every
	 * FETCH notify, that is to say, stop once after the execution of a
	 * integrated instruction. Verbose mode means hold every notify, means will
	 * stop each notify occurs.
	 */
	private static boolean hold = false;
	private static boolean verbose = false;

	private NotifyBus() {

	}

	public static NotifyBus getInstance() {
		if (self == null)
			self = new NotifyBus();

		return self;
	}

	public synchronized void sendNotify(Notify n, Notifiable target) {
		try {
			if (hold && verbose) { // hold and verbose
				this.tmpNotifyBuffer = n;
				this.tmpTargetBuffer = target;
			} else if (hold && n.getType() == Notify.FETCH) { // hold and not
																// verbose
				this.tmpNotifyBuffer = n;
				this.tmpTargetBuffer = target;
			} else {
				// no hold
				target.notify(n);
				target.run();
			}
		} catch (UnexpectedNotifyReceivedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * When this function is called, notify the next target and continue
	 * execution.
	 * 
	 * @throws UnexpectedNotifyReceivedException
	 */
	public void nextStep() throws UnexpectedNotifyReceivedException {
		if (hold)
			try {
				tmpTargetBuffer.notify(tmpNotifyBuffer);
				tmpTargetBuffer.run();
			} catch (NullPointerException e) {
				// when code reach here, means the two buffers are not
				// initialized.
				// just print an alert and continue execution.
				System.err.println(e.getLocalizedMessage());
				return;
			}
	}

	public boolean isHold() {
		return hold;
	}

	public boolean isVerbose() {
		return verbose;
	}

	/**
	 * If you want to enable single step execution, call
	 * {@code NotifyBus.getInstance().setSingleStep(true)}; if you want to
	 * disable single step, then call
	 * {@code NotifyBus.getInstance().setSingleStep(false)}. This will cause it
	 * stop at the beginning at each instruction until
	 * {@code NotifyBus.getInstance().nextStep(false)} is called
	 * 
	 * When single step status changed, Machine Status Register also changed.
	 * 
	 * @param hold
	 */
	public void setSingleStep(boolean hold) {

		System.err.println("single step: " + hold);
		if (hold)
			RegisterGroup.getInstance().setMSR(RegisterGroup.MS_SINGLESTEP);
		else
			RegisterGroup.getInstance().setMSR(RegisterGroup.MS_RUNNING);

		NotifyBus.hold = hold;
	}

	/**
	 * Call {@code NotifyBus.getInstance().setVerbose()} to enable verbose mode.
	 * If you didn't call {@code setSingleStep(true)} before
	 * {@code setVerbose(true)}, it will affect nothing.
	 * 
	 * Under verbose mode, the program will stop at every tiny step. Every
	 * notify on NotifyBus would be hold.
	 * 
	 * @param verbose
	 */
	public void setVerbose(boolean verbose) {
		NotifyBus.verbose = verbose;

	}

}
